import csv
import math
import tkinter as tk
from tkinter import ttk, Tk, Canvas, filedialog, messagebox, simpledialog, font
from PIL import ImageGrab
from PinoutPlanObj import *
from AppConfig import AppConfig
from LeadObj import LeadObj
from LeadNameObj import LeadNameObj
from LeadIDObj import LeadIDObj
from TextPreviewer import TextPreviewer


class App:
    def __init__(self):
        
        self.cfg:AppConfig = AppConfig("PackageOutlineViewer_config.json")
        self.plans:DevicePinoutPlanObj = None
        self.plan:PinoutPlanObj = None
        self.plan_selected : str = ""
        combox_config_options = [] 
        
    

        self.root = tk.Tk()
        self.root.title("Package Outline Preview")
        
        # wxh + x + y
        self.root.geometry("1700x1300+50+50")        
        
        # 创建左侧按钮区域
        self.button_frame = ttk.Frame(self.root)
        self.button_frame.grid(row=0, column=0, sticky=tk.NS)
        button_frame_index = 0
        
        # 添加按钮
        buttons = {
            "新建方案": self.new_plan,
            "选择方案": self.select_plan,
            "重读方案": self.package_reinit_plan,
            "保存为PNG": self.save_canvas_as_png,
            "帮助": self.help_button_clicked

            #"保存bondingTable": self.save_bondingTable
        }
        
        for i, (text, command) in enumerate(buttons.items()):
            btn = ttk.Button(self.button_frame, text=text, command=command)
            btn.grid(row=i, column=0, padx=5, pady=5, sticky=tk.W+tk.E)
            button_frame_index = button_frame_index + 1
            
        # 创建combox

        # 创建 Combobox 组件
        self.combox_config = ttk.Combobox(self.button_frame, values=combox_config_options)
        self.combox_config.grid(row=button_frame_index, column=0, padx=5, pady=5, sticky=tk.W+tk.E)
        button_frame_index = button_frame_index + 1 

        # 绑定选中事件
        self.combox_config.bind("<<ComboboxSelected>>", self.on_combobox_select)

        # 创建状态栏框架
        status_frame = ttk.Frame(self.root)
        status_frame.grid(row=1, column=0, columnspan=2, sticky=tk.W+tk.E)
        
        # 创建只读文本窗口显示scale_factor
        #self.scale_text = tk.Text(status_frame, height=1, width=15, state='disabled')
        #self.scale_text.grid(in_=status_frame, row=0, column=0, padx=5, pady=5, sticky=tk.W+tk.E)
        
        #self.mouse_pos_text = tk.Text(status_frame, height=1, width=15, state='disabled')
        #self.mouse_pos_text.grid(in_=status_frame, row=0, column=1, padx=5, pady=5, sticky=tk.W+tk.E)
                
        # 创建右侧预览区域
        self.preview_frame = ttk.Frame(self.root)
        self.preview_frame.grid(row=0, column=1, sticky=tk.NSEW)
        
        # 配置网格权重
        self.root.grid_columnconfigure(1, weight=1)
        #self.root.grid_columnconfigure(1, weight=8)
        self.root.grid_rowconfigure(0, weight=1)
        
        # 创建画布
        self.canvas = tk.Canvas(self.preview_frame, bg='white')
        self.canvas.pack(fill=tk.BOTH, expand=True)
        
        # 绑定画布大小调整事件
        self.canvas.bind('<Configure>', self.on_configure)
        
        # 初始绘制方框
        self.package_firstinit()
        self.package_preview()
    def view(self) :
        self.root.mainloop()
    def new_plan(self):
        file_path = filedialog.asksaveasfilename(
        defaultextension=".json",
        filetypes=[("Assembly Plan Files", "*.json"), ("All files", "*.*")]
        )
        if file_path:              
            DevicePinoutPlanObj.GetDefault(file_path)

    def select_plan(self):
        file_path = filedialog.askopenfilename(filetypes=[("Assembly Plan Files", "*.json")])
        
        if file_path :
            valid_file = True                     
            self.plans = DevicePinoutPlanObj(file_path)
            if not self.plans.CheckValid():
                valid_file = False
                messagebox.showinfo("Error", "a assembly plan json file or its content is invalid")
                
            if valid_file :    
                self.package_init()
                self.package_preview()
                self.cfg.newrecord(file_path)   
       
    def package_setparas(self):
        self.FontSize = int(self.plan.LeadWidth) #
        self.TextType = 'Arial' # 'Helvetica'
        
        LeadObj.Width = self.plan.LeadWidth  # 焊盘示意框宽
        LeadObj.Height = self.plan.LeadHeight # 焊盘示意框高
        LeadObj.PinIsOutsidePackage = self.plan.PinIsOutsidePackage # 封装类型管脚在外面，如QFP
        
        LeadNameObj.TextHeight = self.FontSize + 2 # Pin名称显示区域高,应该比字号大
        LeadNameObj.TextWidth = self.plan.PinTextBackgroundAreaLength # Pin名称显示区域宽
        LeadNameObj.Offset = 10     # Pin名称距离边的像素距离
        LeadNameObj.TextSize = int(self.plan.LeadWidth * self.plan.FactorPinNameSizeToPinWidth)
        LeadNameObj.TextType = self.TextType
        LeadNameObj.IsBold = self.plan.LeadNameIsBold
        
        LeadIDObj.Offset = 5 + self.plan.LeadHeight # PinID距离边的像素距离
        LeadIDObj.FontSize = int(self.FontSize/2)
        LeadIDObj.Font = self.TextType
        
        self.CenterTextPad = 0       # 中心text多行之间的间隔
        self.CenterTextLocation = (0,0) # 中心text区域的中心坐标
        

    def package_firstinit(self):   
                
        combox_config_options = []
        if self.cfg.check_valid():
            self.plans = DevicePinoutPlanObj(self.cfg.record()) 
            if not self.plans:
                messagebox.showinfo("Error", "a assembly plan json file or its content is invalid")
            else:
                combox_config_options = self.plans.GetPlanNames()
        else:
            messagebox.showinfo("Warning", "Please select a assembly plan json file")
            
        self.combox_config['values'] = combox_config_options
        
        # 设置默认选中项
        if len(combox_config_options) > 0:
            self.combox_config.current(0) # only the first time
            self.combobox_select()      
                
    def package_init(self):  
        combox_config_options = []
        if self.cfg.check_valid():
            self.plans = DevicePinoutPlanObj(self.cfg.record()) 
            if not self.plans:
                messagebox.showinfo("Error", "a assembly plan json file or its content is invalid")
            else:
                combox_config_options = self.plans.GetPlanNames()
        else:
            messagebox.showinfo("Warning", "Please select a assembly plan json file")
            
        self.combox_config['values'] = combox_config_options
        
        # 设置默认选中项
        if len(combox_config_options) > 0:
            self.combobox_select()     
            
    
    def package_preview(self):    
        
        if self.plan == None:
            return        
        
        if self.plan.NumOfEdageWithLeads == 4:   
            self.draw_qfn()
        else:
            self.draw_dfn()
        
    def creat_centertext(self): 
        if self.plan.CenterInfo == None:
            return
        if self.plan.CenterInfo.__len__() == 0:
            return
        
        #先创建文本,获得id和bbox信息
        self.CenterTextInfos = []
        lenOfTexts = [0]
        botemys = [0]
        starty = 0 # y的累计值
        for tinfo in self.plan.CenterInfo:
            thisFont = font.Font(family=self.TextType, size=int(tinfo["FontSize"]),weight=("bold" if tinfo["IsBold"].upper() == "TRUE" else "normal"))
            id = self.canvas.create_text(0, starty, text=tinfo["Text"], font=thisFont,tags="centertext")
            bbox = self.canvas.bbox(id)
            #self.canvas.create_rectangle(bbox[0],bbox[1],bbox[2],bbox[3], fill='yellow', outline='')
            #self.canvas.tag_raise(id)
            textinfo = {}            
            textinfo["id"] =  id
            textinfo["bbox"] =  bbox # x1,y1,x2,y2
            self.CenterTextInfos.append(textinfo)
            lenOfTexts.append(bbox[2]) # 各text的长度
            botemys.append(bbox[3])
            starty = starty + self.CenterTextPad + (bbox[3] - bbox[1]) # 下一行text的y坐标
            
        #计算最长的text,逐txt横向移动到所有text中心对齐
        maxlen = max(lenOfTexts)
        #for info in self.CenterTextInfos :
        #    currentx = info["bbox"][2]/2
        #    movex = maxlen - currentx
        #    self.canvas.move(info["id"], movex, 0)
            
        #计算最大的y,获得CenterTextLocation
        maxy = max(botemys)
        
        # 所有text移动到plan的中心
        movex = self.plan.Width/2# - maxlen/2
        movey = self.plan.Height/2 - maxy/2
        for info in self.CenterTextInfos :
            self.canvas.move(info["id"], movex, movey)

    def draw_qfn(self):         
            
            _y = (self.plan.Height - self.plan.LeadPitch * (self.plan.FirstedgePinSum - 1))/2
            _x = (self.plan.Width - self.plan.LeadPitch * (self.plan.SecondedgePinSum - 1))/2
            self.PinInfos = {}
            pinindex = 0
            for i in range(pinindex, self.plan.FirstedgePinSum) :
                self.PinInfos[i+1] = {'Location' : (0, _y + i * self.plan.LeadPitch), 'edge_pos' : 'L'}
            
            pinindex = pinindex + self.plan.FirstedgePinSum        
            for i in range(0, self.plan.SecondedgePinSum) :
                self.PinInfos[i+1 + pinindex] = {'Location' : (_x + i * self.plan.LeadPitch, self.plan.Height), 'edge_pos' : 'D'}
                
            pinindex = pinindex + self.plan.SecondedgePinSum        
            for i in range(0, self.plan.FirstedgePinSum) :
                self.PinInfos[i+1 + pinindex] = {'Location' : (self.plan.Width,  self.plan.Height - (_y + i * self.plan.LeadPitch)), 'edge_pos' : 'R'}
                
            pinindex = pinindex + self.plan.FirstedgePinSum        
            for i in range(0, self.plan.SecondedgePinSum) :
                self.PinInfos[i+1 + pinindex] = {'Location' : (self.plan.Width - (_x + i * self.plan.LeadPitch),0), 'edge_pos' :  'U'}
            
            # 画外框
            self.package_id = self.canvas.create_rectangle(0, 0, self.plan.Width, self.plan.Height, tags="package")        
            
            # 画EP
            if self.plan.EPExists :
                self.canvas.create_rectangle((self.plan.Width-self.plan.EPWidth)/2, (self.plan.Height-self.plan.EPHeight)/2,
                                            (self.plan.Width+self.plan.EPWidth)/2, (self.plan.Height+self.plan.EPHeight)/2)            
            
            # 画Pin
            for key,value in self.PinInfos.items() :
                if False :
                    # 画锚点
                    org_x = value['Location'][0]
                    org_y = value['Location'][1]
                    radius = 2
                    self.canvas.create_oval(org_x - radius, org_y - radius, org_x + radius, org_y + radius, fill='darkviolet', outline='darkviolet')

                LeadObj(canvas=self.canvas,edge_pos=value['edge_pos'],location=value['Location'])
                LeadIDObj(canvas=self.canvas, edge_pos=value['edge_pos'], location=value['Location'], id_value=key)
                
                ptext = ""
                pfontcolor = 'black'
                pbackgroundcolor = ''
                for pinfo in self.plan.PinInfo:
                    if pinfo['id'] == key:
                        ptext = pinfo['text']
                        pfontcolor = pinfo['fontcolor']
                        pbackgroundcolor = pinfo['backgroundcolor']
                        
                LeadNameObj(canvas=self.canvas, edge_pos=value['edge_pos'], location=value['Location'],text=ptext,colorEnable=self.plan.PinColorEnable,Fontcolor=pfontcolor,Backgroundcolor=pbackgroundcolor)
                
            # 画中心text
            self.creat_centertext() 
                
                
    def draw_dfn(self):         
            
            _y = (self.plan.Height - self.plan.LeadPitch * (self.plan.Pinsum / 2 - 1))/2
            self.PinInfos = {}
            pinindex = 0
            for i in range(pinindex, self.plan.FirstedgePinSum) :
                self.PinInfos[i+1] = {'Location' : (0, _y + i * self.plan.LeadPitch), 'edge_pos' : 'L'}            
                
            pinindex = pinindex + self.plan.FirstedgePinSum        
            for i in range(0, self.plan.FirstedgePinSum) :
                self.PinInfos[i+1 + pinindex] = {'Location' : (self.plan.Width,  self.plan.Height - (_y + i * self.plan.LeadPitch)), 'edge_pos' : 'R'}
                
            # 画外框
            self.package_id = self.canvas.create_rectangle(0, 0, self.plan.Width, self.plan.Height, tags="package")        
            
            # 画EP
            if self.plan.EPExists :
                self.canvas.create_rectangle((self.plan.Width-self.plan.EPWidth)/2, (self.plan.Height-self.plan.EPHeight)/2,
                                            (self.plan.Width+self.plan.EPWidth)/2, (self.plan.Height+self.plan.EPHeight)/2)            
            
            # 画Pin
            for key,value in self.PinInfos.items() :
                if False :
                    # 画锚点
                    org_x = value['Location'][0]
                    org_y = value['Location'][1]
                    radius = 2
                    self.canvas.create_oval(org_x - radius, org_y - radius, org_x + radius, org_y + radius, fill='darkviolet', outline='darkviolet')

                LeadObj(canvas=self.canvas,edge_pos=value['edge_pos'],location=value['Location'])
                LeadIDObj(canvas=self.canvas, edge_pos=value['edge_pos'], location=value['Location'], id_value=key)
                
                ptext = ""
                pfontcolor = 'black'
                pbackgroundcolor = ''
                for pinfo in self.plan.PinInfo:
                    if pinfo['id'] == key:
                        ptext = pinfo['text']
                        pfontcolor = pinfo['fontcolor']
                        pbackgroundcolor = pinfo['backgroundcolor']
                        
                LeadNameObj(canvas=self.canvas, edge_pos=value['edge_pos'], location=value['Location'],text=ptext,colorEnable=self.plan.PinColorEnable,Fontcolor=pfontcolor,Backgroundcolor=pbackgroundcolor)
                
            # 画中心text
            self.creat_centertext() 
    def on_configure(self, event):        
        ccx = self.canvas.winfo_width()/2
        ccy = self.canvas.winfo_height()/2        
        
        bbox = self.canvas.bbox(self.package_id)
        itemcx = (bbox[0]+bbox[2]) /2
        itemcy = (bbox[1]+bbox[3]) /2
        
        # 所有内容平移100 100
        self.canvas.move('all', ccx-itemcx, ccy-itemcy)
        
    def save_canvas_as_png(self):
        # 获取当前屏幕截图
        x = self.canvas.winfo_rootx()
        y = self.canvas.winfo_rooty()
        x1 = x + self.canvas.winfo_width()
        y1 = y + self.canvas.winfo_height()
        image = ImageGrab.grab(bbox=(x+3, y+3, x1-3, y1-3))

        # 打开保存文件对话框
        filename = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG files", "*.png")])
        if filename:
            # 保存图像为PNG格式
            image.save(filename, "PNG")
            
    def package_reinit_plan(self):
        self.canvas.delete("all")
        self.package_init()
        self.package_preview()
        self.on_configure(None)

    def combobox_select(self) :        
        selected_text = self.combox_config.get()
        self.plan_selected = selected_text
        #print(f"Selected Index: {selected_index}, Selected Text: {selected_text}")
        self.plan = self.plans.GetPlanByName(selected_text)
        if self.plan :
            self.root.title(f"Package Outline Preview : {selected_text}")
            self.package_setparas()
    def on_combobox_select(self, event):
        self.combobox_select()
        if self.plan :
            self.canvas.delete("all")            
            self.package_preview()
            self.on_configure(None)

    def get_center_of_all_items(self):
        all_items = self.canvas.find_all()  # 获取Canvas上的所有项
        if not all_items:
            return None  # 如果Canvas为空，则返回None
        
        x0_min, y0_min = float('inf'), float('inf')
        x1_max, y1_max = float('-inf'), float('-inf')
        
        for item in all_items:
            coords = self.canvas.coords(item)  # 获取每个项的坐标
            if coords:
                # 更新最小x0, y0 和 最大 x1, y1
                x0_min = min(x0_min, min(coords[::2]))  # 偶数索引为x坐标
                y0_min = min(y0_min, min(coords[1::2]))  # 奇数索引为y坐标
                x1_max = max(x1_max, max(coords[::2]))
                y1_max = max(y1_max, max(coords[1::2]))
        
        # 计算中心点
        center_x = (x1_max + x0_min) / 2
        center_y = (y1_max + y0_min) / 2    
        return center_x, center_y
    
    def help_button_clicked(self) :
        helpstr = """
帮助
PackageOutLinePreview用于生成预览封装引脚方案并支持保存为bng 文件。
目前仅支持QFN、DFN。
版本：1.1

按键说明：
1. 新建方案  : 生成一个空的方案配置文件
2. 选择方案  : 选择方案配置文件
3. 重读方案  ：重新读取方案配置文件，方便修改后刷新
4. 保存为PNG : 把图像区域保存为PNG格式的图片
5. 帮助      : 展示本内容

本地配置文件 ： PackageOutlineViewer_config.json 用于记录最后打开的方案文件。

方案配置文件 ： xxx.json 具体方案的配置文件，格式说明：
[                             # 数组， 文件包含几个方案就写几个配置block，比如一个系列包含3个封装方案就写3个block
    {                         # 1个block
        "Name": "DFNDemo",    # 方案名称
        "PinIsOutsidePackage": "False", # 封装类型,管脚在包围体外，true/false，比如QFN false, QFP true
        "NumOfEdageWithLeads": 2,       # 有管脚的变数，比如DFN是2，QFN是4， 只能是2或者4
        "Width": 4.0,         # 封装体宽度，单位mm
        "Height": 4.0,        # 封装体高度，单位mm
        "EPExists": "true",   # 有没有EPAD，true/false
        "EPWidth": 2.5,       # 有EPAD 时，EPAD的宽度，单位mm
        "EPHeight": 3.2,      # 有EPAD 时，EPAD的高度，单位mm
        "Pinsum": 8,          # 封装体总管脚数
        "PinTextBackgroundAreaLength": 3.0, # 封装体总脚的文本背景区域长度，单位mm
        "FirstedgePinSum": 4, # 封装体总脚的第一脚所在边上的管脚数量。部分封装体的相邻两边上的管脚数量不一致，用这个区别。
        "LeadWidth": 0.5,     # 管脚示意图像的宽度，单位mm
        "LeadHeight": 0.4,    # 管脚示意图像的高度，单位mm
        "LeadPitch": 0.9,     # 管脚示意图幅的间距，单位mm，中心到中心的距离
        "OneMMForPixs": 100,  # 1mm对应的像素数量，可以理解为图像的放大系数。图中的管脚名称和id的文本大小会跟随实际图像变化。
        "FactorPinNameSizeToPinWidth":0.8, # 管脚名称的文本大小和引脚宽度的比例，比如0.8，那么引脚宽度为200 pix，那么引脚名称的文本大小为200*180=160pix
        "LeadNameIsBold":"true",           # 管脚名称是否加粗，true or false
        "CenterInfo": [            # 中心区域需要显示的内容，数组，有几行文字写几个配置项，按照配置的顺序显示
            {
                "Text":"Demo_DFN", # 文本内容
                "FontSize":25,      # 字号
                "IsBold":"true"     # 是否加粗，true or false
            },
            {
                "Text":"subtext",
                "FontSize":20
                "IsBold":"false"
            }
         ],
        "PinColorEnable":"True",   # 是否显示引脚颜色， true or false, true的话就显示，否则字就是黑色，无背景色
        "PinInfo":[{"id":1,"text":"VDD","fontcolor":"red","backgroundcolor":"white"},     # id 是管脚id，从1开始
                    {"id":2,"text":"GND","fontcolor":"#13f383","backgroundcolor":"yellow"},
                    {"id":0,"text":"VDD","fontcolor":"#e06666","backgroundcolor":"white"}, # id为0的配置项不会被显示，用于隐藏或备忘
                    {"id":7,"text":"ADC Input 1/GPIOA11/PWMCH1N","fontcolor":"#e06666","backgroundcolor":"#1c8a64"}
                ]
    },
    {
        "Name": "QFNDemo",
        "PinIsOutsidePackage": "False",
        "NumOfEdageWithLeads": 4,
        "Width": 5.0,
        "Height": 5.0,
        "EPExists": "true",
        "EPWidth": 3.5,
        "EPHeight": 3.5,
        "Pinsum": 48,
        "PinTextBackgroundAreaLength": 3.0,
        "FirstedgePinSum": 12,
        "LeadWidth": 0.2,
        "LeadHeight": 0.4,
        "LeadPitch": 0.3,
        "OneMMForPixs": 100,
        "FactorPinNameSizeToPinWidth":0.8,
        "LeadNameIsBold":"true",
        "CenterInfo": [
            {
                "Text":"Demo_QFN",
                "FontSize":20
                "IsBold":"true"
            },
            {
                "Text":"subtext",
                "FontSize":18
                "IsBold":"true"
            }
         ],
        "PinColorEnable":"True",
            "PinInfo":[{"id":1,"text":"VDD","fontcolor":"red","backgroundcolor":"white"},
                       {"id":2,"text":"GND","fontcolor":"#13f383","backgroundcolor":"yellow"},
                       {"id":0,"text":"VDD","fontcolor":"#e06666","backgroundcolor":"white"},
                       {"id":7,"text":"ADC Input 1/GPIOA11/PWMCH1N","fontcolor":"#e06666","backgroundcolor":"#1c8a64"}
                    ]
    }
] 
        """
        TextPreviewer(helpstr,1200,1200)
        

if __name__ == "__main__":    
    App().view()

